#pragma once 
#include<iostream>
#include<string>
#include<cstring>
#include<unistd.h>
#include<sys/wait.h>
#include<sys/types.h>
#include<sys/socket.h>
#include<arpa/inet.h>
#include<netinet/in.h>
#include<pthread.h>
#include<signal.h>
#include"Log.hpp"
#include"ThreadPool.hpp"
#include"Task.hpp"
#include"Daemon.hpp"

using namespace std;

const int defaultfd=-1;
const string defaultip="0.0.0.0";//这是万能ip表示任何地址
const int backlog=10;//设置最大10个客户端用户进入等待连接队列
Log lg;//在server中定义日志对象

enum//使用枚举类型代替宏作为错误码的定义
{
    UsageError=1,
    SocketError,
    BindError,
    ListenError
};

class TcpServer;

//构建一个发送给线程的数据块
class ThreadData
{
public:
    ThreadData(int fd,const string&ip,const uint16_t &p,TcpServer *t)
        :sockfd(fd),clientip(ip),clientport(p),tsvr(t)
    {}
public:
    int sockfd;//套接字对应的文件描述符
    string clientip;//客户端ip
    uint16_t clientport;//客户端端口
    TcpServer*tsvr;//服务器对象
};

class TcpServer
{
public:
    TcpServer(const uint16_t &port,const string&ip=defaultip)
        :listensock_(defaultfd),port_(port),ip_(ip)
    {}
    void InitServer()
    {
        listensock_=socket(AF_INET,SOCK_STREAM,0);//创建一个新的套接字。AF_INET: 这是一个宏，指定了地址族。AF_INET代表IPv4地址。SOCK_STREAM: 这是一个宏，指定了套接字类型。SOCK_STREAM代表提供序列化、可靠、双向连接的基于字节的流（即TCP）。0: 这是协议号，通常设置为0，表示使用默认协议。
        //返回值是对应的文件描述符
        if(listensock_<0)
        {
            lg(Fatal,"create socket,,errno:%d,errstring:%s",errno,strerror(errno));
            exit(SocketError);//程序结束，返回我们设定的对应的错误码
        }
        lg(Info,"create socket success,listensock_:%d",listensock_);

        int opt=1;
        setsockopt(listensock_,SOL_SOCKET,SO_REUSEADDR|SO_REUSEPORT,&opt,sizeof(opt));//防止偶发性的服务器无法立即重启
        //这行代码将SO_REUSEADDR和SO_REUSEPORT选项同时设置在套接字上。这样做的目的通常是为了：SO_REUSEADDR: 在开发阶段或者在需要频繁重启服务的情况下，可以避免因为套接字还处于TIME_WAIT状态而导致的“地址已在使用”错误。SO_REUSEPORT: 在需要多个进程或线程绑定到同一端口时使用，这在某些高性能服务器或需要实现特定并发模型的场景中很有用。

        struct sockaddr_in local;//把服务器的地址信息存储在local结构体中
        memset(&local,0,sizeof(local));
        local.sin_family=AF_INET;
        local.sin_port=htons(port_);//这行代码的作用是将 port_ 变量中的端口号从主机字节序转换为网络字节序，并将其赋值给 local 结构体的 sin_port 成员。这样做是为了确保当数据在网络上传输时，端口号的表示是统一的，无论主机的字节序是大端还是小端。
        inet_aton(ip_.c_str(),&(local.sin_addr));//将一个点分十进制的IP地址字符串转换为网络字节序的二进制IP地址，并将其设置到一个 sockaddr_in 结构体的 sin_addr 成员中。

        //接下来要给这个套接字绑定端口和ip,也就是绑定sockaddr*类型的local结构体（因为服务器的信息都在该结果体中设置好了）
        if(bind(listensock_,(struct sockaddr*)&local,sizeof(local))<0)
        {
            lg(Fatal,"bind error,errno:%d,errstring:%s",errno,strerror(errno));
            exit(BindError);
        }
        lg(Info,"bind socket success,listensock_:%d",listensock_);

        //Tcp是面向连接的，服务器一般是被动的等待连接
        //listen 函数是在C语言中用于将一个套接字从CLOSED状态转变为LISTEN状态的关键函数，它允许套接字开始接受传入的连接请求。这个函数通常在服务器端使用，以便它能够“监听”来自客户端的连接请求。
        if(listen(listensock_,backlog)<0)//backlog=10，表示该监听套接字客户端连接的等待队列最多有10个客户端在等待连接处理
        //解释：backlog 参数指定了内核应该为相应套接字排队的最大连接个数。这个参数定义了在套接字接受连接之前，操作系统可以挂起的连接请求的最大数量。也就是最多支持10个客户端一起发送连接请求，这些请求会被挂起形成一个队列，然后由服务器一个个的accept连接，并且随着accept，又可以有新的客户端连接请求被挂起到这个请求队列中，最大挂起的请求个数为10
        {
            lg(Fatal,"listen error,errno:%d,errstring:%s",errno,strerror(errno));
            exit(ListenError);
        }
        lg(Info,"listen socket success,listensock_:%d",listensock_);
    }
    //初始化：完成了套接字的创建与绑定，并且对该套接字启动了监听客户端的请求


    void Start()//启动服务器
    {
        Daemon();//运行起来的服务器改为守护进程在后台持续运行
        ThreadPool<Task>::GetInstance()->Start();//启动线程池
        lg(Info,"TcpServer is running......");
        for(;;)
        {
            //获取新连接：
            struct sockaddr_in client;//存储连接服务器的客户端的信息
            socklen_t len=sizeof(client);

            //初始创建的套接字listensock_被设定为监听的状态，所以我们才能使用accept从这个套接字的请求连接队列中取出一个并创立一个对应的套接字文件描述符
            int sockfd=accept(listensock_,(struct sockaddr*)&client,&len);//从监听队列中取出一个连接请求，并为该连接创建一个新的套接字
            //accept 函数返回一个新的文件描述符，这个文件描述符代表了与该客户端的连接
            if(sockfd<0)//与该客户端建立连接失败并不是什么重要的事故，继续连接下一个即可
            {
                lg(Warning,"accept error,errno:%d,errstring:%s",errno,strerror(errno));
                continue;
            }
            //建立连接后，客户端的信息就会存储在client结构体中

            //我们可以获取客户端的端口号等信息
            uint16_t clientport=ntohs(client.sin_port);//ntohs是把网络字节序的端口号转为主机字节序，可以赋值给16位无符号整形变量，表示端口号
            char clientip[32];
            inet_ntop(AF_INET,&(client.sin_addr),clientip,sizeof(clientip));//按照格式把网络字节序的ip地址转为主机字符串形式存储在字符数组中

            //根据新连接就可以通信：
            Task t(sockfd,clientip,clientport);//创建任务并且把当前连接的文件描述符和地址一起发送给任务
            ThreadPool<Task>::GetInstance()->Push(t);//把任务推送到任务队列，让线程池对任务进行处理

            //至此，服务器就完成了对一个客户端的一个连接的响应，就可以开始处理下一个客户端的连接了
        }
    }
    ~TcpServer(){}

private:
    int listensock_;//监听的套接字文件
    uint16_t port_;//服务器端口
    string ip_;//服务器ip
};

